package flexo.scene;

import java.util.LinkedList;
import java.util.List;

import flexo.common.LogMsgType;
import flexo.math.Matrix;
import flexo.math.Quaternion;
import flexo.math.Vector;


/**
 * A scene-node capable of mantaining transformation matrices.
 * @author Miguel
 */
public class Node
{
	// HIERARCHY ///////////////////////////////////////
	
	protected String name;	
	
	protected Node parent = null;
	protected List<Node> children;
		
	Node(String name)
	{
		this.name = name;
		
		absPosition = new Vector();
		position = new Vector();
		rotation = new Quaternion();
		scale = new Vector(1,1,1);
		relativeMatrix = new Matrix();
		absoluteMatrix = new Matrix();
		updateRelativeMatrix = true;
		updateAbsoluteMatrix = true;
		updateAbsPosition = true;
		
		children = new LinkedList<Node>();
	}
	
	public String getName()
	{
		return name;
	}
	
	public Node getParent()
	{
		return parent;
	}
	
	public Scene getScene()
	{
		if (parent == null)
		{
			return (Scene)this;
		}
		else
		{
			return parent.getScene();
		}
	}
	
	protected void setParent(Node parent)
	{
		this.parent = parent;
	}
	
	public Node addChild(String name)
	{
		Node n = new Node(name);
		n.setParent(this);
		this.children.add(n);
		return n;
	}
	
	public void remove()
	{
		if (parent != null)
		{
			parent.removeChild(this);
		}
	}
	
	void removeChild(Node node)
	{
		if (children.contains(node))
		{
			node.parent = null;
			children.remove(node);
		}			
	}
	
	public Node getChild(String name)
	{
		Node n = null;
		n = getChildRecursive(name);
		if (n == null)
		{
			flexo.Kernel.log("flexo.scene.Node", "'"+name+"' node not found", LogMsgType.WARNING);
		}
		return n;
	}
	
	protected Node getChildRecursive(String name)
	{
		if (this.name.equals(name))
		{
			return this;
		}
		else
		{
			for (Node n : children)
			{
				Node found = n.getChildRecursive(name);
				if (found != null)
				{
					return found;
				}
			}
		}
		return null;
	}
	
	// TRANSFORMATION //////////////////////////////////
	
	private Vector position;
	private Vector absPosition;
	protected boolean updateAbsPosition;
	private Quaternion rotation;
	private Vector scale;
	
	protected boolean updateRelativeMatrix;
	protected Matrix relativeMatrix;
	
	protected Matrix absoluteMatrix;
	protected boolean updateAbsoluteMatrix;
	
	/**
	 * When a node changes its position/rotation, it notifies all of its children.
	 */
	protected void propagateParentChange()
	{
		updateAbsoluteMatrix = true;
		
		for (Node n: children)
		{
			n.propagateParentChange();
		}
	}
	
	public Matrix getRelativeTransformation()
	{
		if (updateRelativeMatrix)
		{
			relativeMatrix.setRotation(rotation);
			Matrix m = Matrix.getTempMatrix(); m.setScale(scale);
			Matrix.mul(m, relativeMatrix, relativeMatrix);
			m.setPosition(position);
			Matrix.mul(m, relativeMatrix, relativeMatrix);
			updateRelativeMatrix = false;
		}
		return relativeMatrix;
	}
	
	public Matrix getAbsoluteTransformation()
	{
		if (updateAbsoluteMatrix || updateRelativeMatrix)
		{
			updateAbsoluteMatrix();
			updateAbsPosition = true;
		}
		
		return absoluteMatrix;
	}
	
	public Vector getAbsolutePosition()
	{
		if (updateAbsPosition || updateAbsoluteMatrix || updateRelativeMatrix)
		{
			updateAbsPosition = false;
			absPosition.reset();
			getAbsoluteTransformation().transform(absPosition);
		}
		
		return absPosition;
	}
	
	protected void updateAbsoluteMatrix()
	{
		if (parent != null)
		{
			Matrix.mul( getParent().getAbsoluteTransformation(),
					getRelativeTransformation(),
					absoluteMatrix);
		}
		else
		{
			absoluteMatrix.load(getRelativeTransformation());
		}		
		updateAbsoluteMatrix = false;
	}
	
	public Vector getPosition()
	{
		return position;
	}
	
	public Vector getScale()
	{
		return scale;
	}
	
	public void move(Vector displacement)
	{
		position.move(displacement);
		updateRelativeMatrix = true;
		propagateParentChange();
	}
	
	public void rotate(Quaternion rotation)
	{
		this.rotation.rotate(rotation);
		updateRelativeMatrix = true;
		propagateParentChange();
	}

	public void setPosition(Vector position)
	{
		this.position.set(position);
		updateRelativeMatrix = true;
		propagateParentChange();
	}
	
	public void setScale(Vector scale)
	{
		this.scale.set(scale);
		updateRelativeMatrix = true;
		propagateParentChange();
	}
	
	public void setScale(float scale)
	{
		this.scale.x = scale;
		this.scale.y = scale;
		this.scale.z = scale;
		updateRelativeMatrix = true;
		propagateParentChange();
	}
	
	public Quaternion getRotation()
	{
		return rotation;
	}

	public void setRotation(Quaternion rotation)
	{
		this.rotation.set(rotation);
		updateRelativeMatrix = true;
		propagateParentChange();
	}	
	
	public String toString()
	{
		return name;
	}
}